package org.transformatorhuis.unix.server;

import com.sun.jna.StringArray;
import org.productivity.java.syslog4j.Syslog;
import org.productivity.java.syslog4j.SyslogIF;
import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Logger;

import static org.transformatorhuis.unix.server.THLibrary.THLIB;

/**
 * User: Olivier Van Acker <cyberroadie@transformatorhuis.net>
 * Date: 31-Mar-2010
 */
public abstract class AbstractServer implements Server {

    private Logger logger = Logger.getLogger(this.getClass().getName());

    private static final int SIGTERM = 15;
    protected static final String MODE_PROPERTY = CloneableServer.class.getName() + ".mode";
    protected static final String MODE = "thread";
    protected List<String> arguments;
    protected THSignalHandler thSignalHandler = new THSignalHandler();
    protected SyslogIF syslog = Syslog.getInstance("udp");
    protected Object processThread;

    protected void instantiate(String[] args, Object processThread) throws RTFMException {
        this.arguments = Collections.unmodifiableList(Arrays.asList(args));
        if (processThread instanceof Runnable)
            this.processThread = processThread;
        else {
            throw new RTFMException("Thread object doesn't implement Runnable interface");
        }
        if (!(processThread instanceof Observer))
            logger.warning("Thread doesn't implement Observer interface, will not get a message when JVM shuts down");
        thSignalHandler.addObserver(this);
        Signal.handle(new Signal("TERM"), thSignalHandler);
    }

    /**
     * Defaults to 1 JVM and 1 thread
     *
     * @throws IOException
     */
    @Override
    public void run() throws IOException {
        run(1, 1);
    }

    @Override
    public abstract void run(int forks, int threadsPerProcess) throws IOException;

    @Override
    public void update(Observable o, Object arg) {
        File file = new File("server." + THLIB.getpid());
        file.delete();
        syslog.warn("Exit process");
        System.exit(0);
    }

    protected class THSignalHandler extends Observable implements SignalHandler {

        @Override
        public void handle(Signal signal) {
            switch (signal.getNumber()) {
                case (SIGTERM):
                    setChanged();
                    notifyObservers();
                    break;
            }
        }
    }

    /**
     * Gets the current executable name.
     */
    protected String getCurrentExecutable() {
        int pid = THLIB.getpid();
        String name = "/proc/" + pid + "/exe";
        if (new File(name).exists())
            return name;

        // cross-platform fallback
        return System.getProperty("java.home") + "/bin/java";
    }

    protected static void thread(Runnable runnable, boolean daemon) {
        Thread serverThread = new Thread(runnable);
        serverThread.setDaemon(daemon);
        serverThread.start();
    }

    protected void forkProcess() throws IOException {
        CommandLine commandLine = CommandLineFactory.getCommandLine();
        String exe = getCurrentExecutable();
        commandLine.setSystemProperty(MODE_PROPERTY, MODE);
        StringArray sa = new com.sun.jna.StringArray(commandLine.toArray(new String[commandLine.size()]));

        System.out.println(arguments.toString());
        System.out.println(System.getProperties());

        int r = THLIB.fork();
        if (r < 0) {
            syslog.error("forking process failed");
            System.exit(-1);
        }
        if (r == 0) {
            THLIB.execv(exe, sa);
            syslog.error("initial exec failed");
            System.exit(-1);
        }

    }

    public static class RTFMException extends RuntimeException {

    public RTFMException(String message) {
        super("You didn't implement it right: " + message);
    }

}

}
